{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 使用selenium和12306web版获取时刻表  \n",
    "<b>selenium需要配置对应的webdriver，详细教程在网上有</b>    \n",
    "\n",
    "\n",
    "## 当前状态  \n",
    "- 主要由于12306本身购票属性远大于信息查询故在时刻表等信息层面提供信息偏少  \n",
    "    - 如交路表等信息较为贫乏,无车站大屏等实时查询能力  \n",
    "    - 对于单个车次的信息查询效果较好，如车次变化（虽然没用上）等  \n",
    "    - 检票口(映射股道)需要依照车次再次查询如网页版12306中检票口查询  \n",
    "\n",
    "- 目前爬取效果,处理等和前面武汉枢纽相同\n",
    "    - 基本实现自动化,但是由于12306各个页面本身编写风格差别较大,难以在这里通过切换子html来完成不同查询,这里还是采用直接获取不同网页而不是首页跳转来实现  \n",
    "    - 目前重点为完善信息源，获得按照车站查询的能力，但是大部分第三方网页车次信息不准<b>尤其是上下行切换基本被判断为不同车次</b>\n",
    "    - 完善度表格:    <br>\n",
    "\n",
    "|组件信息|完善程度|当前状态|日后计划|  \n",
    "|---|---|---|---| \n",
    "车站|没有信息源|寻找相对准确的数据源并完善这部分|暂无\n",
    "车次|信息较为完善|可以获得单一车次给出的大部分信息如停站检票口等.<br>主要为从交路表查询获得|考虑压缩为字典样式以更好的存储信息\n",
    "检票口/股道 | 较为完善|若需要检票口信息需要事先获取车次信息后再查询检票口|考虑完善映射方法    \n",
    "立折车辆 | 纯手动，没有处理|手动查找修改|寻找可靠的信息源|计划在车次查询部分加上交路表部分信息获取\n",
    "越行车辆 |纯手动，完全没有处理|没有计划|不知道怎么获知越行车信息\n",
    "  \n",
    "## 使用的库函数信息和达成的基本效果\n",
    "<b>主要为了提高复用性功能大体分为了三个部分，数据处理可以单独由excel或csv使用无需使用爬虫</b>  \n",
    "- 获取整体信息-自动化爬虫部分\n",
    "    - selenium进行web自动化，使用pandas承接结果以更好的格式化数据  \n",
    "    - 一般的io文本处理部分，输出为csv样式的txt文本文件\n",
    "- 初步信息处理-数据处理/数据分析\n",
    "    - 整理出较为规整的数据，主要按照车次来汇总数据便于后续操作\n",
    "    - 输出符合游戏地图要求的时刻表信息或是进行其他分析  \n",
    "- 没啥用的可视化\n",
    "    - 主要是pyecharts，输出一些可交互的图片更为直观\n",
    "\n",
    "----\n",
    "## 第一部分，定义使用的库函数和常量信息\n",
    "### 内容\n",
    "库函数主要分为三大部分：获取数据--处理数据--<del>分析（可视化）数据</del>。即下面引用部分大体分为了自动化和数据处理两部分  \n",
    "而常量部分主要用于处理数据生成符合游戏格式的时刻表，这部分会随着函数的完善而更为精简  \n",
    "  \n",
    "### 待完善/优化的部分\n",
    "1. 编组映射待交路表信息获取完成后会按照车型映射如长编和重连，即使用下面marshalling字典\n",
    "2. 速度等级也会按照车型信息重新映射\n",
    "3. 建立进场路径映射相关常量来完善分场式车站可能的调度问题，提高调度自动化程度\n",
    "4. ....."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "import selenium\n",
    "from selenium import webdriver\n",
    "from selenium.webdriver.common.keys import Keys  # 引用keys包\n",
    "from selenium.webdriver.common.by import By\n",
    "from selenium.webdriver.common.action_chains import ActionChains\n",
    "from selenium.common.exceptions import StaleElementReferenceException\n",
    "import time\n",
    "import pandas #当前爬取部分pandas用于承接数据以获取更好的格式\n",
    "import threading\n",
    "import queue\n",
    "\n",
    "\n",
    "trainType = [\"普客\", \"新空调快速\", \"新空调特快\", \"新空调直快\", \"动车\", \"高速\", \"城际\"]\n",
    "stationRegion = [\"济南\", \"济南西\", \"济南东\", \"大明湖\"]\n",
    "\n",
    "\n",
    "Excelpath = \"data.xlsx\"  # Excel时刻表输入文件路径，如爬取的时刻表或是是复制来的\n",
    "TextPath = \"train.txt\"  # 游戏时刻表文件输出路径\n",
    "# 速度和编组以及类型映射关系,0为普速1为动车2为高速，后续修改\n",
    "species = {'新空调普快': ['120', 'LPPPPPP', 0], '新空调快速': ['120', 'LPPPPPP', 0],\n",
    "           '新空调特快': ['140', 'LPPPPPP', 0], '新空调直快': ['160', 'LPPPPPP', 0],\n",
    "           '动车': ['200', 'LPPL', 1], '城际': ['200', 'LPPL', 1], '高速': ['300', 'LPPLLPPL', 2]}\n",
    "species1 = {'K': ['120', 'LPPPPPP', 0], 'T': ['140', 'LPPPPPP', 0], 'Z': ['160', 'LPPPPPP', 0],\n",
    "            'D': ['200', 'LPPL', 1], 'C': ['200', 'LPPL', 1], 'G': ['300', 'LPPLLPPL', 2]}\n",
    "\n",
    "# 车站-编号,掉向,用时以及运行车辆种类映射关系\n",
    "# 图片左(0)右(1)侧线路key值相同则掉向,\n",
    "# 国铁车辆行走左侧,2为数据为左侧股道编号\n",
    "# [车站编号,车站所在侧(0为左侧),车辆进场股道,车辆离场行走股道,到达中心车站所用时间]\n",
    "station = {\n",
    "    '京济联络线济南方向': ['b', 0, 2, 1, 8], '济郑高速长清方向': ['c', 0, 2, 1, 7],\n",
    "    '济南西站': ['d', -1, 0, 0, 7], '济南西动车所': ['e', -1, 0, 0, 30],\n",
    "    '京沪高速德州东方向': ['f', 1, '1', '2', 6, ], '京沪高速泰安站方向': ['a', 0, 2, 1, 7, ],\n",
    "    '石济客专齐河方向': ['g', 1, 1, 2, 6], '石济客专济南东方向': ['h', 1, 1, 2, 6, ],\n",
    "}\n",
    "\n",
    "# 线路和车站关系，主要用于从车站-值获取线路-键\n",
    "route1 = {\n",
    "    '济南西动车所': [\"济南西\"], '京济联络线济南方向': [\"济南\"], '济郑高速长清方向': [\"长清\"],\n",
    "    '京沪高速泰安站方向': [\"泰安\", \"曲阜东\", \"滕州东\", \"枣庄\", \"徐州东\", \"宿州东\", \"蚌埠南\", \"南京南\"],\n",
    "    '京沪高速德州东方向': [\"北京南\", \"天津西\", \"天津\", \"沧州西\", \"德州东\"],\n",
    "    '石济客专齐河方向': [\"齐河\", \"禹城东\", \"平原东\"], '石济客专济南东方向': [\"济南东\"]}\n",
    "# 两个合在一起写太难看\n",
    "\n",
    "\n",
    "# 车型关系--待完善，后续待交路表部分完成后会使用对应映射而不是上面的简单类型映射\n",
    "marshalling = {'CHR380BL': [], 'CHR380B': [], 'CHR380B重连': []}\n",
    "\n",
    "\n",
    "ThisStation = '济南西站'\n",
    "TotalPlat = 18\n",
    "# track = {}\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第二部分，selenium自动化爬取12306上时刻表等信息\n",
    "### 内容\n",
    "这部分主要有两个函数，分别信息查询--时刻表查询和信息查询--检票口查询获取该车次所有信息。12306本身没有按照车站来查询信息的部分，故车次需要预先获取\n",
    "1. 车次时刻表信息  \n",
    "    1.1 这部分较为简单，爬取时信息较为规整，查询结果本身为规则的表格，且信息全部位于第一个单元格内，按照正常顺序获取处理即可。\n",
    "    1.2 对于所有车次通过循环获取数据\n",
    "2. 车次检票口信息 \n",
    "    2.1 这部分难度相对较大，主要难点在于车站选择为不可输入的非下拉选择框，必须遍历点选 \n",
    "    2.2 循环获取查询返回结果，和车站车次一起写入文件便于合并\n",
    "\n",
    "\n",
    "### 效果\n",
    "1. 对于已知(传入的)的车次可以获取全部如时刻表等信息\n",
    "\n",
    "### 实现原理  \n",
    "1. selenium按流程进入页面，由于两个页面特征不一样，无法通过首页加跳转来访问 \n",
    "2. 输入车次信息并确认查询来获取对应信息\n",
    "3. 使用list和dataframe来收集数据以优化格式  \n",
    "### 待完善/优化的部分\n",
    "1. 获取新的信息源来进行车站查询\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "def getEntance(wd: webdriver.Firefox, TrainNumList: list[str], station: str, fn: str):\n",
    "    # 检票口查询\n",
    "    wd.get(url='https://www.12306.cn/index/view/infos/ticket_check.html')\n",
    "    entrance = \"\"  # 承接检票口信息\n",
    "    res = []\n",
    "    # 输入车次并确认\n",
    "    NumInput = wd.find_element(By.ID, 'ticket_check_trainNum')\n",
    "    searchbtn = wd.find_element(By.CSS_SELECTOR, \".btn\")\n",
    "\n",
    "    for TrainNum in TrainNumList:\n",
    "        NumInput.clear()\n",
    "        NumInput.send_keys(TrainNum+'\\n')\n",
    "        time.sleep(1)  # 输入后自动选择第一个，无需点选\n",
    "        NumInput.send_keys(Keys.ENTER)  # 回车键Enter\n",
    "        # 输入后自动选择第一个，无需点选\n",
    "\n",
    "        # 点击下拉框以获得停站选项\n",
    "        # time.sleep(1)         # 下拉框\n",
    "        # wd.find_element(By.CSS_SELECTOR, '#ticketEntranceSel').click()\n",
    "\n",
    "        #点击下拉框以获得停站选项\n",
    "        time.sleep(1)  # 下拉框\n",
    "        wd.find_element(By.XPATH,'//*[@id=\"ticketEntranceSel\"]').click()\n",
    "\n",
    "        time.sleep(1)# 选择停站\n",
    "        elements = wd.find_elements(By.CSS_SELECTOR, \".model-select-option > li\")\n",
    "        for ele in elements:  # 在所有停站中遍历信息\n",
    "            if ele.text == station:  # 如果查询到\n",
    "                \n",
    "                ele.click()  # 点击下拉框对应选项确认\n",
    "                searchbtn.click()  # 点击查询按钮\n",
    "                checkin = wd.find_elements(By.CLASS_NAME, \"check-numnew\")\n",
    "                time.sleep(0.5)\n",
    "                entrance = checkin[0].text  # 得到检票口文本信息\n",
    "                break\n",
    "        print([TrainNum, station, entrance])\n",
    "        res.append([TrainNum, station, entrance])\n",
    "        \n",
    "    entdt = pandas.DataFrame(data=res, columns=[\"车次\", \"车站\", \"检票口\"])\n",
    "    print(entdt)\n",
    "    entdt.to_csv(path_or_buf=fn, sep=\",\",mode=\"a\" ,index=False, header=False)\n",
    "    return None\n",
    "\n",
    "\n",
    "# for index, row in sheet.iterrows():\n",
    "#     e=getEntance(wd,row[\"车次\"])\n",
    "#     row[\"检票口\"]=e\n",
    "\n",
    "def getRoute(wd: webdriver.Firefox, TrainNumList: list[str], fn: str):\n",
    "    # 时刻表查询\n",
    "    wd.get(url=\"https://kyfw.12306.cn/otn/queryTrainInfo/init\")\n",
    "    # 承接返回结果\n",
    "    routedt = pandas.DataFrame(\n",
    "        columns=[\"站序\", \"车站\", \"车次\", \"出发时间\", \"到达时间\", \"历时\", \"天数\"])\n",
    "    res = []\n",
    "    NumInput = wd.find_element(By.ID, \"numberValue\")  # 车次输入框\n",
    "    searchbtn = wd.find_element(By.CLASS_NAME, \"btn122s\")  # 查找按钮\n",
    "    # 设置为utf8，防止如复兴号，静音动车等符号导致报错\n",
    "    # f=open(file=fn,mode=\"w\",encoding=\"utf8\")\n",
    "    for TrainNum in TrainNumList:\n",
    "        NumInput.clear()  # 先清空之前内容\n",
    "        NumInput.send_keys(TrainNum[:-1])\n",
    "        time.sleep(1)\n",
    "        NumInput.send_keys(TrainNum[-1])\n",
    "        time.sleep(1)  # 分两次输入便于联想到正确车次信息\n",
    "\n",
    "        NumInput.click()  # 点击获得联想车次的下拉列表\n",
    "        # 点击第一个车次，即为输入的车次\n",
    "        wd.find_element(By.CSS_SELECTOR,\n",
    "                        \"#train_hide > li:nth-child(1)\").click()\n",
    "        time.sleep(1)\n",
    "        searchbtn.click()  # 点击选择按钮\n",
    "\n",
    "        infoTb = wd.find_element(By.ID, \"queryTable\")  # 信息表格，表头和内容\n",
    "        rows = infoTb.find_elements(By.TAG_NAME, \"td\")  # 表格内容部分\n",
    "        for i in range(0, len(rows), 12):  # 只承接车次停站信息部分\n",
    "            try:\n",
    "                tem = (rows[i].text).split(sep=\"\\n\")\n",
    "                tem[2] = TrainNum  # 防止由于上下行车次变化导致分组时出现异常\n",
    "                res.append(tem)\n",
    "                # print(res) #奇怪的时间异常\n",
    "            except StaleElementReferenceException as e:\n",
    "                print(res)\n",
    "                print(\"车次：{0}，出现查询异常：{2}\".format(TrainNum, e))\n",
    "        \n",
    "        routedt = pandas.DataFrame(data=res,\n",
    "                                columns=[\"站序\", \"车站\", \"车次\", \"出发时间\", \"到达时间\", \"历时\", \"天数\"])\n",
    "        routedt.drop(columns=[\"站序\", \"天数\"], axis=1, inplace=True)\n",
    "        # print(routedt)\n",
    "        routedt.to_csv(path_or_buf=fn,mode=\"a+\" ,sep=\",\", encoding=\"utf8\",\n",
    "                    index=False, header=False)\n",
    "    \n",
    "    return None\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 传入车次列表来爬取相应信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['G311', '济南西', '检票口5B']\n",
      "['G2661', '济南西', '检票口6B']\n",
      "['G1909', '济南西', '检票口4B']\n",
      "['G2697', '济南西', '检票口16A']\n",
      "['G345', '济南西', '检票口13A']\n",
      "['G295', '济南西', '检票口7B']\n",
      "['G1830', '济南西', '检票口2B']\n",
      "['G279', '济南西', '检票口4B']\n",
      "['G1050', '济南西', '检票口11B']\n",
      "['G5293', '济南西', '检票口7B']\n",
      "['G1054', '济南西', '检票口11B']\n",
      "['G103', '济南西', '检票口7B']\n",
      "['G384', '济南西', '检票口10B']\n",
      "['G441', '济南西', '检票口6A']\n",
      "['G171', '济南西', '检票口5B']\n",
      "['G390', '济南西', '检票口12B']\n",
      "['G386', '济南西', '检票口11B']\n",
      "['G45', '济南西', '检票口7B']\n",
      "['G396', '济南西', '检票口13B']\n",
      "['D2778', '济南西', '检票口17A']\n",
      "['G2572', '济南西', '检票口10B']\n",
      "['G2663', '济南西', '检票口2B']\n",
      "['G2551', '济南西', '检票口7B']\n",
      "['G2636', '济南西', '检票口11A']\n",
      "['G5369', '济南西', '检票口4B']\n",
      "['G173', '济南西', '检票口7B']\n",
      "['G105', '济南西', '检票口5B']\n",
      "['G2553', '济南西', '检票口4B']\n",
      "['D1644', '济南西', '检票口2A']\n",
      "['G107', '济南西', '检票口6B']\n",
      "['G1568', '济南西', '检票口11B']\n",
      "['G2648', '济南西', '检票口12B']\n",
      "['G31', '济南西', '检票口7B']\n",
      "['G3', '济南西', '检票口4B']\n",
      "['G887', '济南西', '检票口6B']\n",
      "['G1566', '济南西', '检票口10B']\n",
      "['G109', '济南西', '检票口5B']\n",
      "['G5297', '济南西', '']\n",
      "['G375', '济南西', '检票口7A']\n",
      "['G197', '济南西', '检票口5B']\n",
      "['G2552', '济南西', '检票口11B']\n",
      "['G111', '济南西', '检票口6B']\n",
      "['G1596', '济南西', '检票口12B']\n",
      "['G177', '济南西', '检票口7B']\n",
      "['G2578', '济南西', '检票口11B']\n",
      "['G261', '济南西', '检票口5A']\n",
      "['G179', '济南西', '检票口6B']\n",
      "['G2', '济南西', '检票口10B']\n",
      "['G4', '济南西', '检票口12B']\n",
      "['G2058', '济南西', '检票口14A']\n",
      "['G33', '济南西', '检票口7B']\n",
      "['G5', '济南西', '检票口5B']\n",
      "['G2555', '济南西', '检票口6B']\n",
      "['G2582', '济南西', '检票口10B']\n",
      "['G2580', '济南西', '检票口12B']\n",
      "['G102', '济南西', '检票口11B']\n",
      "['G113', '济南西', '检票口7B']\n",
      "['G321', '济南西', '检票口5B']\n",
      "['G263', '济南西', '检票口4A']\n",
      "['G115', '济南西', '检票口7B']\n",
      "['G2571', '济南西', '检票口3B']\n",
      "['G32', '济南西', '检票口12B']\n",
      "['G6', '济南西', '检票口11B']\n",
      "['G117', '济南西', '检票口5A']\n",
      "['G119', '济南西', '检票口6B']\n",
      "['D1601', '济南西', '']\n",
      "['G172', '济南西', '检票口10B']\n",
      "['G104', '济南西', '检票口14B']\n",
      "['G35', '济南西', '检票口7B']\n",
      "['G2584', '济南西', '检票口12B']\n",
      "['G7', '济南西', '检票口6B']\n",
      "['G1862', '济南西', '检票口17B']\n",
      "['G106', '济南西', '检票口11B']\n",
      "['G169', '济南西', '检票口5B']\n",
      "['G2815', '济南西', '检票口3A']\n",
      "['G110', '济南西', '检票口14B']\n",
      "['G265', '济南西', '检票口6A']\n",
      "['D1604', '济南西', '检票口2A']\n",
      "['G121', '济南西', '检票口5B']\n",
      "['G108', '济南西', '检票口12B']\n",
      "['G329', '济南西', '检票口6A']\n",
      "['G2554', '济南西', '检票口10B']\n",
      "['G323', '济南西', '检票口4B']\n",
      "['G8', '济南西', '检票口13B']\n",
      "['G267', '济南西', '检票口6A']\n",
      "['G2641', '济南西', '']\n",
      "['G376', '济南西', '检票口17B']\n",
      "['G301', '济南西', '检票口5B']\n",
      "['G123', '济南西', '检票口7B']\n",
      "['G112', '济南西', '检票口11B']\n",
      "['G445', '济南西', '检票口6A']\n",
      "['G37', '济南西', '检票口5B']\n",
      "['G9', '济南西', '检票口7B']\n",
      "['G1232', '济南西', '检票口10B']\n",
      "['G114', '济南西', '检票口14B']\n",
      "['G181', '济南西', '检票口5B']\n",
      "['G2586', '济南西', '检票口12B']\n",
      "['G2556', '济南西', '检票口10B']\n",
      "['G1224', '济南西', '检票口11A']\n",
      "['G1209', '济南西', '检票口4B']\n",
      "['G174', '济南西', '检票口14B']\n",
      "['G2624', '济南西', '检票口12A']\n",
      "['G125', '济南西', '检票口5B']\n",
      "['G127', '济南西', '检票口6B']\n",
      "['G10', '济南西', '检票口11B']\n",
      "['G129', '济南西', '检票口7B']\n",
      "['G131', '济南西', '检票口5B']\n",
      "['G176', '济南西', '检票口10B']\n",
      "['G1273', '济南西', '检票口6B']\n",
      "['G1236', '济南西', '检票口13A']\n",
      "['G1223', '济南西', '检票口4A']\n",
      "['G378', '济南西', '检票口14B']\n",
      "['G11', '济南西', '检票口6B']\n",
      "['G116', '济南西', '检票口11A']\n",
      "['G178', '济南西', '检票口12B']\n",
      "['G133', '济南西', '检票口7B']\n",
      "['G1274', '济南西', '检票口14B']\n",
      "['G303', '济南西', '检票口5B']\n",
      "['G1204', '济南西', '检票口13A']\n",
      "['G325', '济南西', '检票口6B']\n",
      "['G2573', '济南西', '检票口7B']\n",
      "['G42', '济南西', '检票口12B']\n",
      "['G135', '济南西', '检票口4B']\n",
      "['G12', '济南西', '检票口10B']\n",
      "['G118', '济南西', '检票口11B']\n",
      "['G1227', '济南西', '检票口6A']\n",
      "['G302', '济南西', '检票口12B']\n",
      "['G2558', '济南西', '检票口14B']\n",
      "['G41', '济南西', '检票口7B']\n",
      "['G1214', '济南西', '检票口11A']\n",
      "['G13', '济南西', '检票口5B']\n",
      "['G382', '济南西', '检票口13B']\n",
      "['G122', '济南西', '检票口10B']\n",
      "['G137', '济南西', '检票口6B']\n",
      "['G1258', '济南西', '检票口14A']\n",
      "['G183', '济南西', '检票口7B']\n",
      "['G124', '济南西', '检票口12B']\n",
      "['G139', '济南西', '检票口6B']\n",
      "['G2596', '济南西', '检票口14B']\n",
      "['G182', '济南西', '检票口11B']\n",
      "['G1251', '济南西', '检票口7B']\n",
      "['G14', '济南西', '检票口13B']\n",
      "['G185', '济南西', '检票口5B']\n",
      "['G141', '济南西', '检票口6B']\n",
      "['G2646', '济南西', '检票口12A']\n",
      "['G187', '济南西', '检票口7B']\n",
      "['G180', '济南西', '检票口11B']\n",
      "['G15', '济南西', '检票口5B']\n",
      "['G1252', '济南西', '检票口12B']\n",
      "['G2575', '济南西', '检票口5B']\n",
      "['G128', '济南西', '检票口14B']\n",
      "['G1228', '济南西', '检票口11A']\n",
      "['G170', '济南西', '检票口12B']\n",
      "['G2557', '济南西', '检票口6B']\n",
      "['G143', '济南西', '检票口4B']\n",
      "['G1213', '济南西', '检票口5A']\n",
      "['G2562', '济南西', '检票口10B']\n",
      "['G184', '济南西', '检票口12B']\n",
      "['G145', '济南西', '检票口7B']\n",
      "['G2662', '济南西', '']\n",
      "['G2623', '济南西', '检票口6A']\n",
      "['G16', '济南西', '检票口11B']\n",
      "['G147', '济南西', '检票口5B']\n",
      "['G34', '济南西', '检票口13B']\n",
      "['G2564', '济南西', '检票口12B']\n",
      "['D1603', '济南西', '']\n",
      "['G2561', '济南西', '检票口6B']\n",
      "['G17', '济南西', '检票口7B']\n",
      "['G130', '济南西', '检票口10B']\n",
      "['G322', '济南西', '检票口11B']\n",
      "['G132', '济南西', '检票口12B']\n",
      "['G149', '济南西', '检票口5B']\n",
      "['G134', '济南西', '检票口10B']\n",
      "['G189', '济南西', '检票口7B']\n",
      "['G2577', '济南西', '检票口6B']\n",
      "['G1203', '济南西', '检票口5A']\n",
      "['D1606', '济南西', '检票口4A']\n",
      "['G330', '济南西', '检票口12A']\n",
      "['G18', '济南西', '检票口14A']\n",
      "['G1861', '济南西', '检票口1A']\n",
      "['G191', '济南西', '检票口6B']\n",
      "['G186', '济南西', '检票口13B']\n",
      "['G43', '济南西', '检票口7B']\n",
      "['G19', '济南西', '检票口3B']\n",
      "['G198', '济南西', '检票口10B']\n",
      "['G377', '济南西', '检票口2A']\n",
      "['G1210', '济南西', '']\n",
      "['G379', '济南西', '检票口1A']\n",
      "['G138', '济南西', '检票口13B']\n",
      "['G151', '济南西', '检票口6B']\n",
      "['G140', '济南西', '检票口11B']\n",
      "['G1257', '济南西', '检票口7A']\n",
      "['G888', '济南西', '检票口12B']\n",
      "['G193', '济南西', '检票口6B']\n",
      "['G195', '济南西', '检票口7B']\n",
      "['G20', '济南西', '检票口11B']\n",
      "['G1567', '济南西', '检票口5B']\n",
      "['G1231', '济南西', '检票口6B']\n",
      "['G280', '济南西', '']\n",
      "['G190', '济南西', '检票口11B']\n",
      "['G153', '济南西', '检票口3B']\n",
      "['G1235', '济南西', '检票口6A']\n",
      "['G142', '济南西', '检票口12B']\n",
      "['G324', '济南西', '检票口11B']\n",
      "['G381', '济南西', '检票口7B']\n",
      "['G2057', '济南西', '']\n",
      "['G380', '济南西', '检票口12B']\n",
      "['G192', '济南西', '检票口11B']\n",
      "['G157', '济南西', '检票口6B']\n",
      "['G2563', '济南西', '检票口7A']\n",
      "['G442', '济南西', '检票口10A']\n",
      "['G144', '济南西', '检票口12B']\n",
      "['G159', '济南西', '检票口5B']\n",
      "['G264', '济南西', '检票口11A']\n",
      "['D2777', '济南西', '']\n",
      "['G2566', '济南西', '检票口10B']\n",
      "['G22', '济南西', '检票口13B']\n",
      "['G1595', '济南西', '检票口6B']\n",
      "['G146', '济南西', '检票口12B']\n",
      "['G161', '济南西', '检票口7B']\n",
      "['G266', '济南西', '检票口11A']\n",
      "['G2581', '济南西', '检票口6B']\n",
      "['G304', '济南西', '检票口12B']\n",
      "['G23', '济南西', '检票口7B']\n",
      "['G196', '济南西', '检票口10B']\n",
      "['G25', '济南西', '检票口5B']\n",
      "['G5294', '济南西', '']\n",
      "['G2645', '济南西', '检票口7A']\n",
      "['G194', '济南西', '检票口11B']\n",
      "['G148', '济南西', '检票口12B']\n",
      "['G1047', '济南西', '检票口3B']\n",
      "['G36', '济南西', '检票口10B']\n",
      "['G1565', '济南西', '检票口6B']\n",
      "['G24', '济南西', '检票口13B']\n",
      "['G150', '济南西', '检票口11B']\n",
      "['G1045', '济南西', '']\n",
      "['G326', '济南西', '检票口12B']\n",
      "['G2588', '济南西', '检票口10B']\n",
      "['G152', '济南西', '检票口11B']\n",
      "['G2585', '济南西', '检票口5B']\n",
      "['G385', '济南西', '检票口6B']\n",
      "['G2816', '济南西', '检票口16A']\n",
      "['G2587', '济南西', '检票口6B']\n",
      "['G268', '济南西', '检票口12A']\n",
      "['G2565', '济南西', '检票口4B']\n",
      "['G2642', '济南西', '']\n",
      "['G154', '济南西', '检票口11B']\n",
      "['G2568', '济南西', '检票口10B']\n",
      "['G38', '济南西', '检票口12B']\n",
      "['G26', '济南西', '检票口11B']\n",
      "['G46', '济南西', '检票口13B']\n",
      "['G2589', '济南西', '检票口6B']\n",
      "['G2635', '济南西', '检票口7A']\n",
      "['G446', '济南西', '检票口11A']\n",
      "['G156', '济南西', '检票口13B']\n",
      "['G158', '济南西', '检票口10B']\n",
      "['G2590', '济南西', '检票口12B']\n",
      "['G389', '济南西', '检票口7B']\n",
      "['G2664', '济南西', '']\n",
      "['G1049', '济南西', '']\n",
      "['G160', '济南西', '检票口11B']\n",
      "['G270', '济南西', '检票口10A']\n",
      "['G383', '济南西', '检票口6B']\n",
      "['G395', '济南西', '检票口5B']\n",
      "['G44', '济南西', '检票口12B']\n",
      "['G162', '济南西', '检票口11B']\n",
      "['G296', '济南西', '']\n",
      "['G5298', '济南西', '']\n",
      "['G1829', '济南西', '']\n",
      "['G2647', '济南西', '检票口7B']\n",
      "['D1643', '济南西', '']\n",
      "['G1243', '济南西', '']\n",
      "['G348', '济南西', '']\n",
      "['G1910', '济南西', '']\n",
      "['G1053', '济南西', '']\n",
      "['G344', '济南西', '']\n",
      "['G5370', '济南西', '']\n",
      "['G312', '济南西', '']\n",
      "['G2698', '济南西', '']\n",
      "        车次   车站     检票口\n",
      "0     G311  济南西   检票口5B\n",
      "1    G2661  济南西   检票口6B\n",
      "2    G1909  济南西   检票口4B\n",
      "3    G2697  济南西  检票口16A\n",
      "4     G345  济南西  检票口13A\n",
      "..     ...  ...     ...\n",
      "274  G1053  济南西        \n",
      "275   G344  济南西        \n",
      "276  G5370  济南西        \n",
      "277   G312  济南西        \n",
      "278  G2698  济南西        \n",
      "\n",
      "[279 rows x 3 columns]\n"
     ]
    }
   ],
   "source": [
    "\n",
    "def getData(trainList: list[str],targetSt:str):\n",
    "    wd = webdriver.Firefox()\n",
    "    wd.implicitly_wait(10)\n",
    "\n",
    "    act = ActionChains(driver=wd)\n",
    "\n",
    "    # 时刻表查询\n",
    "    # getRoute(wd, trainList, \"test12306.txt\")\n",
    "    # 检票口查询\n",
    "    getEntance(wd, trainList, targetSt, \"test12306ent.txt\")\n",
    "    \n",
    "    wd.close()\n",
    "    wd.quit()\n",
    "\n",
    "\n",
    "targetSt = \"济南西\"\n",
    "# 济南Info是之前爬取的济南地区车站信息\n",
    "stationFrame = pandas.read_csv(filepath_or_buffer=\"济南Info.txt\",\n",
    "                               sep=\",\", encoding=\"utf-8\")\n",
    "stationFrame.columns = [\"车次\", \"drop1\", \"停站\",\n",
    "                        \"到时\", \"开时\", \"始发站\", \"终到站\", \"列车类型\", \"drop2\"]\n",
    "# 删去重复列和空列\n",
    "stationFrame.drop_duplicates(inplace=True)\n",
    "stationFrame.drop(columns=[\"drop1\", \"drop2\"], inplace=True)\n",
    "\n",
    "# 获取所有济南西站的车次信息\n",
    "jnzlist = stationFrame[stationFrame[\"停站\"] == targetSt][\"车次\"].to_list()\n",
    "getData( jnzlist,\"济南西\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第三部分，处理获取到的数据并生成游戏样式的时刻表\n",
    "这部分最为重要，按照游戏中时刻表格式分为若干部分  \n",
    "车辆信息部分，如车次编号，速度等级等信息  \n",
    "停站信息部分，有四部分按顺序分别为 停站编号，停站股道，停站时刻，停站时间。这四部分会单独拆开分别生成  \n",
    "例： G7 COMMUTER 300 LPPLLPPL X1 : f#1#11:17:00#0 d#6#11:23:00#3 a#1#11:33:00#0  \n",
    "\n",
    "内容|完成度|实现原理|近期改良计划|远期优化计划\n",
    "---|---|---|---|---\n",
    "车辆信息|较为完善|目前为仅按照车次编号和类型生成|近期暂无|完善交路表爬取以修改速度级和编组信息立折车次信息完善\n",
    "停站信息-停站编号|完善|依照游戏自身设置直接映射|暂无|暂无\n",
    "停站信息-停站股道|较为完善|selenium按照web自动化12306查询获取 <br>appnium部分按照车次信息查询--检票口获得|完善车次查询部分以获得<br>停站检票口信息|使用车站大屏来获得无法离线查询的车站信息\n",
    "停站信息-停站时刻|完善|按照到站时间和出发时间推算|近期暂无|加上线路所映射行车进路部分\n",
    "停站信息-停站时间|完善|按照到站时间和出发时间计算|近期暂无|立折车次停站时间等按照交路表推算\n",
    "整体上|较为完善|使用apply(lambda) 来进列映射整处理而不是按照行单个处理|暂无  \n",
    "\n",
    "### 第三部分第〇小部分--处理用函数定义\n",
    "#### 内容  \n",
    "目前按照上面输入为上面自动化爬取获取到csv样式的txt文件，输出为按照车站为单位的数据  \n",
    "1. 首先去除空行和重复值等基本操作进行数据提纯   \n",
    "2. 在统一处理如得到部分信息如停站时间和始发终到的处理，  \n",
    "3. 可以获得中心车站的停站时间信息等  \n",
    "4. 同时预先定义需要使用的处理函数  \n",
    "\n",
    "#### 实现原理  \n",
    "1. 整体上以处理dataframe为主，文件→dataframe→文本\n",
    "2. 大量使用.apply相应处理函数来处理列，尽可能少使用匿名函数来增加可读性\n",
    "3. 由文件读出的大表来初步处理groupby 再对想到较小的表来处理\n",
    "4.\n",
    "\n",
    "\n",
    "#### 待完善/优化的部分\n",
    "近期暂无计划，远期计划加入进路映射以使用线路所来优化自动化  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas  # 当前处理部分pandas用于处理数据以获取游戏时刻表\n",
    "import time\n",
    "import numpy\n",
    "import random\n",
    "import datetime\n",
    "species = {'新空调普快': ['120', 'LPPPPPP', 0], '新空调快速': ['120', 'LPPPPPP', 0],\n",
    "           '新空调特快': ['140', 'LPPPPPP', 0], '新空调直快': ['160', 'LPPPPPP', 0],\n",
    "           '动车': ['200', 'LPPL', 1], '城际': ['200', 'LPPL', 1], '高速': ['300', 'LPPLLPPL', 2]}\n",
    "species1 = {'K': ['120', 'LPPPPPP', 0], 'T': ['140', 'LPPPPPP', 0], 'Z': ['160', 'LPPPPPP', 0],\n",
    "            'D': ['200', 'LPLPPLPL', 1], 'C': ['200', 'LPPL', 1], 'G': ['300', 'LPPLLPPL', 2]}\n",
    "station = {\n",
    "    '京济联络线济南方向': ['b', 0, 2, 1, 8], '济郑高速长清方向': ['c', 0, 2, 1, 7],\n",
    "    '济南西站': ['d', -1, 0, 0, 7], '济南西动车所': ['e', -1, 0, 0, 30],\n",
    "    '京沪高速德州东方向': ['f', 1, 1, 2, 6, ], '京沪高速泰安站方向': ['a', 0, 2, 1, 7, ],\n",
    "    '石济客专齐河方向': ['g', 1, 1, 2, 6], '石济客专济南东方向': ['h', 1, 1, 2, 6, ],\n",
    "}\n",
    "\n",
    "# 线路和车站关系，主要用于从车站-值获取线路-键\n",
    "route1 = {\n",
    "    '济南西动车所': [\"济南西\"], '京济联络线济南方向': [\"济南\"], \n",
    "    '京沪高速泰安站方向': [\"泰安\", \"曲阜东\", \"滕州东\", \"枣庄\", \"徐州东\", \"宿州东\", \"蚌埠南\", \"南京南\"],\n",
    "    '京沪高速德州东方向': [\"北京南\", \"天津西\", \"天津\", \"沧州西\", \"德州东\"],\n",
    "    '石济客专齐河方向': [\"齐河\", \"禹城东\", \"平原东\"], '石济客专济南东方向': [\"济南东\"],\n",
    "    '济郑高速长清方向':[\"新乡东站\"\"濮阳东站\",\"聊城西\",\"茌平南\",\"长清\"]}\n",
    "# 两个合在一起写太难看\n",
    "\n",
    "# # 边缘列表\n",
    "# entrancejn = {  # 济南西\n",
    "#     \"大漠刘线路所#4#24:00:00#0\":('石济客专济南东方向',None),\n",
    "#     \"大漠刘线路所#3#24:00:00#0\":('石济客专齐河方向',None)\n",
    "# }\n",
    "\n",
    "# entrancewh={ \n",
    "\n",
    "\n",
    "# }\n",
    "\n",
    "def dealTime(datafr: pandas.DataFrame) -> pandas.DataFrame:\n",
    "    # 对于始发车和终到车进行时间处理，默认相同\n",
    "    for idx, row in datafr.iterrows():\n",
    "        if row[\"开时\"] == \"----\":\n",
    "            row[\"开时\"] = row[\"到时\"]\n",
    "        elif row[\"到时\"] == \"----\":\n",
    "            row[\"到时\"] = row[\"开时\"]\n",
    "\n",
    "    # 计算停站时间\n",
    "    datafr[\"到时\"] = pandas.to_datetime(datafr[\"到时\"], errors=\"ignore\")\n",
    "    datafr[\"开时\"] = pandas.to_datetime(datafr[\"开时\"], errors=\"ignore\")\n",
    "    # 实发终到设置停站时间\n",
    "    # pd[\"停站时间\"].replace(0,10,inplace=True)\n",
    "    return datafr\n",
    "\n",
    "\n",
    "def stopStTime(stopTime: pandas.Timedelta) -> int:\n",
    "    a = int(stopTime.seconds/60)  # 处理为数值类型的分钟时间\n",
    "    # 始发或是终到的时间是随机的\n",
    "    return a if a > 0 else random.randint(10, 30)\n",
    "\n",
    "\n",
    "def ModtimeStr(switchTime: pandas.Timestamp) -> str:\n",
    "    # 处理为仅有时分秒样式的字符串格式的时间\n",
    "    return switchTime.strftime(\"%H:%M:%S\")\n",
    "\n",
    "\n",
    "def speMarType(traincode: str) -> str:  # 目前获取编组信息的方式\n",
    "    til = species1[traincode[0]]  # 处理获取车辆信息字符串部分\n",
    "    return \"{cod}|{cod} COMMUTER {speed} {mar} X1\".format(cod=traincode, speed=til[0], mar=til[1])\n",
    "\n",
    "\n",
    "def checkin(entrance: str) -> int:\n",
    "    # 终到车\n",
    "    res = 0\n",
    "    if entrance == \"无信息\":\n",
    "        res = 0\n",
    "    else:  # 格式类似检票口15A，替换掉其他字样\n",
    "        res = int(entrance.strip(\"检票口AB\"))\n",
    "    return res\n",
    "\n",
    "\n",
    "def prevnextST(stopSt: list[str], stIdx: int) -> list[str]:\n",
    "    # 返回结果，获取目标车站的前后车站\n",
    "    res = [\"\", \"\"]\n",
    "    res1 = [\"\", \"\"]\n",
    "    if stIdx == 0:  # 始发车视为始发车站和下一站\n",
    "        res = [stopSt[0], stopSt[1]]\n",
    "    elif stIdx == len(stopSt)-1:  # 终到车视为前一站和终到站\n",
    "        res = [stopSt[len(stopSt)-2], stopSt[len(stopSt)-1]]\n",
    "    else:  # 中间站\n",
    "        res = [stopSt[stIdx-1], stopSt[stIdx+1]]\n",
    "    # 判断并修改进路\n",
    "    for k, v in route1.items():\n",
    "        # 把车站名映射为线路\n",
    "        if res[0] in v:\n",
    "            res1[0] = k\n",
    "        elif res[1] in v:\n",
    "            res1[1] = k\n",
    "    return res1\n",
    "\n",
    "\n",
    "def arrLeaTime(t1: pandas.Timestamp, st: list[str], mark: int) -> str:\n",
    "    # 选取是上一站还是下一站\n",
    "    tarst = st[mark]\n",
    "    useTime = station[tarst][4]\n",
    "    if mark == 0:  # 进场减时间\n",
    "        res = t1-datetime.timedelta(minutes=useTime)\n",
    "    else:  # 离场加时间\n",
    "        res = t1+datetime.timedelta(minutes=useTime)\n",
    "    # 返回时分秒格式的字符串\n",
    "    return res.strftime(\"%H:%M:%S\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 第三部分第一小部分--爬取信息整体初步处理 \n",
    "#### 内容\n",
    "读取爬取到的时刻表和检票口两个文件，进行数据清洗等常规操作，修改整理数据格式。之后按照车次来合并两个文件/frame。\n",
    "\n",
    "#### 实现原理\n",
    "常规数据清洗方法  \n",
    "\n",
    "#### 待完善/优化的部分\n",
    "暂无，考虑改为流式读取（虽然不大但是几万行）以改善观瞻"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 手动建立映射关系,别的方法不会\n",
    "\n",
    "\n",
    "def processTrains(routefile: str, enterfile:str,targetSt: str) -> pandas.DataFrame:\n",
    "    # 处理产生初步所需信息的数据框，车次时刻表部分\n",
    "    trainRouteFrame = pandas.read_csv(filepath_or_buffer=routefile,\n",
    "                                  sep=\",\", encoding=\"utf8\", )\n",
    "    trainRouteFrame.rename(columns={\"出发时间\":\"开时\",\"到达时间\":\"到时\"},inplace=True) \n",
    "    trainRouteFrame.drop(columns=[\"历时\",\"天数\"], inplace=True)\n",
    "    trainRouteFrame.drop_duplicates(inplace=True)\n",
    "    # 检票口部分\n",
    "    trainEntframe=pandas.read_csv(filepath_or_buffer=enterfile,\n",
    "                                  sep=\",\", encoding=\"utf8\")\n",
    "    trainEntframe.fillna(value=\"无信息\",inplace=True)\n",
    "    trainRouteFrame = dealTime(trainRouteFrame)  # 初步处理时间\n",
    "    trainRouteFrame.sort_values(by=[\"车次\", \"到时\"], inplace=True)  # 归类排序\n",
    "    # 整理为分钟样式的停站时间\n",
    "    #trainsFrame[\"停站时间\"] = trainsFrame[\"停站时间\"].apply(lambda x: stopStTime(x))\n",
    "    trainRouteFrame.reset_index(drop=True, inplace=True)\n",
    "    # 按照车次分组为列表，同时车次不作为索引以便于操作\n",
    "    tfgp = trainRouteFrame.groupby(by=[\"车次\"], as_index=False).agg(list)\n",
    "    # 获取目标停站的索引值，设置为一个辅助列\n",
    "    tfgp[\"auxIdx\"] = tfgp['车站'].apply(lambda x: x.index(targetSt))\n",
    "\n",
    "    # 获取前后停站以得到更好的交路映射\n",
    "    tfgp[\"目标前后站\"] = tfgp.apply(\n",
    "        lambda x: prevnextST(x[\"车站\"], x[\"auxIdx\"]), axis=1)\n",
    "    # 获取其他信息\n",
    "    tfgp[\"目标站到时\"] = tfgp.apply(lambda x: x[\"到时\"][x[\"auxIdx\"]], axis=1)\n",
    "    tfgp[\"目标站开时\"] = tfgp.apply(lambda x: x[\"开时\"][x[\"auxIdx\"]], axis=1)\n",
    "    # tfgp[\"目标站检票口\"] = tfgp[\"检票口\"]\n",
    "    # tfgp[\"目标站检票口\"] = tfgp.apply(lambda x: x[\"检票口\"][x[\"auxIdx\"]], axis=1)\n",
    "    tfgp=pandas.merge(left=tfgp,right=trainEntframe,how=\"left\",on=\"车次\",suffixes=(None,\"目标\"))\n",
    "    # print(tfgp.head())\n",
    "\n",
    "    # 提取需要的信息为新的dataframe\n",
    "    trainInfoNeed = tfgp[[\"车次\", \"目标站到时\", \"目标站开时\", \"检票口\", \"目标前后站\"]]\n",
    "    trainInfoNeed.columns = [\"车次\", \"到时\", \"开时\", \"检票口\", \"前后站\"]\n",
    "    print(trainInfoNeed.head())\n",
    "\n",
    "    return trainInfoNeed\n",
    "\n",
    "def routedispatch(x, mark=0) -> str:\n",
    "    # 对于分场式车站按线路所分流进场车辆,目前仅设置进场线路所路由\n",
    "    #print(stoptruck,\"\\t\" ,routearr[mark])\n",
    "    t = (x[\"到时\"]-datetime.timedelta(minutes=2)).strftime(\"%H:%M:%S\")\n",
    "    stoptruck = x[\"检票口\"]\n",
    "    if stoptruck >= 1 and stoptruck <= 14:  # 高速场车辆\n",
    "        k1 = {\"武九客专葛店南方向\": f\" p#3#{t}#0\",  # 武汉南线路所\n",
    "              \"沪蓉线汉口方向\": f\" n#2#{t}#0\",  # 滠口线路所\n",
    "              \"丹水池联络线汉口方向\": f\" n#2#{t}#0\"}\n",
    "    else:  # 综合场车辆\n",
    "        k1 = {\n",
    "            \"武九客专葛店南方向\": f\" p#1#{t}#0\",\n",
    "            \"沪蓉线汉口方向\": f\" n#1#{t}#0\",\n",
    "            \"丹水池联络线汉口方向\": f\" n#1#{t}#0\"}\n",
    "\n",
    "    return k1.get(x[\"前后站\"][mark], \"\")\n",
    "\n",
    "\n",
    "\n",
    "def tostrformat(row1):\n",
    "    res = \"\"\n",
    "    try:\n",
    "        indexst = station.get(row1[\"车站名称\"])\n",
    "        res = \" {0}#{1}#{2}#{3}\".format(\n",
    "            indexst[0], row1[\"股道\"], row1[\"到达时间\"], row1[\"停站时间\"])\n",
    "    except KeyError:\n",
    "        print(\"KeyError in:\", str(row1))\n",
    "    finally:\n",
    "        return res\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  第三部分第二小部分 -- 处理生成最终dataframe样式的数据 \n",
    "\n",
    "#### 内容\n",
    "按照上面的函数处理数据，最终处理生成有进(离)场时间/股道 等信息的dataframe,之后生成字符串导出    \n",
    "这里分为两部分，车辆信息的trainframe和路径时刻表的stationframe,  \n",
    " 'D8176 COMMUTER 200 LPPL X1 : b#2#18:08:00#0 d#0#18:16:00#14 e#0#18:36:00#0 ',\n",
    "\n",
    "#### 实现原理\n",
    "> 1. generateTrainInfo 即为文字版时刻表分号前的内容，生成车次信息部分  \n",
    ">> 1.1. 考虑到内容只需做车次编号，速度等级和编组信息则按照车次首位ktz生成    \n",
    ">> 2.1. 编组信息尚未准备开始   \n",
    "> 2. generateArriveLeave 即为文字版时刻表分号后的内容，生成进场和离场信息部分  \n",
    ">> 2.1. 基于停站的上一站完善进路的刻画\n",
    ">> 2.2. 本身进场和离场处理基本类似，这里使用一个mark来进行区分\n",
    "> 3. generateMainSt 即为区域核心车站生成部分  \n",
    ">> 3.1. 绝大多数信息已经在前面处理完成，这里主要是处理格式问题  \n",
    ">> 3.2. 车站名称的填充放在最后，防止由于数据全空导致填充失败  \n",
    "\n",
    "#### 改进计划  \n",
    "1. 对于分场式车站采用进场线路--对应线路所来映射，加入线路所映射部分\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "def generateArriveLeave(datafr: pandas.DataFrame, mark) -> pandas.DataFrame:\n",
    "    # 生成进场/离场信息\n",
    "    # mark=0为进场，mark=1为离场\n",
    "    ALStDF = pandas.DataFrame(columns=['车站名称', '股道', '到达时间', '停站时间'])  # 进场信息\n",
    "    # 按照信息来映射进场/离场线路\n",
    "    ALStDF['车站名称'] = datafr[\"前后站\"].apply(lambda x: x[mark])\n",
    "    # 按照信息来映射进场/离场股道\n",
    "    try:\n",
    "        ALStDF['股道'] = ALStDF['车站名称'].apply(lambda x: station[x][2+mark])\n",
    "    except KeyError as ke:\n",
    "        print(\"KeyError?\")\n",
    "    # 时间作差\n",
    "    \n",
    "    # 进程默认经过不停车\n",
    "    ALStDF['停站时间'] = 0\n",
    "    if mark == 0:  # 对于进场车辆看是否有分场需要\n",
    "        ALStDF['到达时间'] = datafr.apply(\n",
    "        lambda x: arrLeaTime(x[\"到时\"], x[\"前后站\"], mark), axis=1)\n",
    "        ALStDF[\"线路所\"] = datafr.apply( #进场的加入线路所部分\n",
    "            lambda x: routedispatch(x, mark), axis=1)\n",
    "    else: #离场车辆\n",
    "        ALStDF['到达时间'] = datafr.apply(\n",
    "        lambda x: arrLeaTime(x[\"开时\"], x[\"前后站\"], mark), axis=1)\n",
    "    # print(ALStDF.head())\n",
    "    ALStDF[\"字符格式\"] = ALStDF.apply(lambda x: tostrformat(x), axis=1)\n",
    "    return ALStDF\n",
    "\n",
    "\n",
    "def generateMainSt(datafr: pandas.DataFrame, mainst: str):\n",
    "    # 生成中心停站信息\n",
    "    MainStDF = pandas.DataFrame(columns=['车站名称', '股道', '到达时间', '停站时间'])  # 进场信息\n",
    "\n",
    "    # 计算停站时长，得到整形类型的时长，非浮点数没有.0便于写入文件\n",
    "    MainStDF[\"停站时间\"] = (datafr[\"开时\"]-datafr[\"到时\"]\n",
    "                        ).apply(lambda x: stopStTime(x))\n",
    "    # 计算并规整进场离场和停站时间，将三个到时改为游戏时分秒格式 hh:mm:ss\n",
    "    MainStDF[\"到达时间\"] = datafr[\"到时\"].apply(lambda x: ModtimeStr(x))\n",
    "\n",
    "    # 按照相应格式处理车站检票口为对应形式\n",
    "    # MainStDF[\"股道\"] = datafr[\"检票口\"].apply(lambda x: checkin(x))\n",
    "    MainStDF[\"股道\"] = datafr[\"检票口\"] #.apply(lambda x: checkin(x))\n",
    "\n",
    "    # 填充车站为中心车站\n",
    "    MainStDF[\"车站名称\"].fillna(value=mainst, inplace=True)\n",
    "    MainStDF[\"字符格式\"] = MainStDF.apply(lambda x: tostrformat(x), axis=1)\n",
    "    return MainStDF\n",
    "\n",
    "\n",
    "def generateTrainInfo(dataser: pandas.Series) -> pandas.Series:\n",
    "    # 生成车次信息\n",
    "    res = dataser.apply(lambda x: speMarType(x))\n",
    "    return res\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第四部分 -- 生成对应样式的字符串并写入文件\n",
    "\n",
    "### 内容  \n",
    "1. 整合之前的数据来生成符合样式的dataframe数据合集  \n",
    "2. 生成csv长字符串并按行分割为一个个的端数据  \n",
    "3. 最终连接处理生成结果，不替换代号便于后续检查    \n",
    "\n",
    "### 实现原理\n",
    "整合数据和字符串处理\n",
    "\n",
    "### 改进计划  \n",
    "暂无"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "      车次                  到时                  开时    检票口  \\\n",
      "0  G2661 2023-10-22 10:12:00 2023-10-22 10:10:00  检票口6B   \n",
      "1  G2664 2023-10-22 21:39:00 2023-10-22 21:39:00    无信息   \n",
      "2   G280 2023-10-22 18:06:00 2023-10-22 18:06:00    无信息   \n",
      "3   G311 2023-10-22 06:16:00 2023-10-22 06:16:00  检票口5B   \n",
      "\n",
      "                      前后站  \n",
      "0  [京沪高速泰安站方向, 京沪高速德州东方向]  \n",
      "1     [京沪高速泰安站方向, 济南西动车所]  \n",
      "2     [京沪高速泰安站方向, 济南西动车所]  \n",
      "3     [济南西动车所, 京沪高速泰安站方向]  \n",
      "0    G2661 COMMUTER 300 LPPLLPPL X1\n",
      "1    G2664 COMMUTER 300 LPPLLPPL X1\n",
      "2     G280 COMMUTER 300 LPPLLPPL X1\n",
      "3     G311 COMMUTER 300 LPPLLPPL X1\n",
      "Name: 车次, dtype: object\n",
      "G2661 COMMUTER 300 LPPLLPPL X1 : 京沪高速泰安站方向#2#10:05:00#0 济南西#6#10:12:00#1438 京沪高速德州东方向#2#10:18:00#0\n",
      "G2664 COMMUTER 300 LPPLLPPL X1 : 京沪高速泰安站方向#2#21:32:00#0 济南西#0#21:39:00#10 济南西动车所#0#22:09:00#0\n",
      "G280 COMMUTER 300 LPPLLPPL X1 : 京沪高速泰安站方向#2#17:59:00#0 济南西#0#18:06:00#21 济南西动车所#0#18:36:00#0\n",
      "G311 COMMUTER 300 LPPLLPPL X1 : 济南西动车所#0#05:46:00#0 济南西#5#06:16:00#18 京沪高速泰安站方向#1#06:23:00#0\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "['G2661 COMMUTER 300 LPPLLPPL X1 : 京沪高速泰安站方向#2#10:05:00#0 济南西#6#10:12:00#1438 京沪高速德州东方向#2#10:18:00#0',\n",
       " 'G2664 COMMUTER 300 LPPLLPPL X1 : 京沪高速泰安站方向#2#21:32:00#0 济南西#0#21:39:00#10 济南西动车所#0#22:09:00#0',\n",
       " 'G280 COMMUTER 300 LPPLLPPL X1 : 京沪高速泰安站方向#2#17:59:00#0 济南西#0#18:06:00#21 济南西动车所#0#18:36:00#0',\n",
       " 'G311 COMMUTER 300 LPPLLPPL X1 : 济南西动车所#0#05:46:00#0 济南西#5#06:16:00#18 京沪高速泰安站方向#1#06:23:00#0']"
      ]
     },
     "execution_count": 68,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "def generateStr(fn: str)->list:\n",
    "    #生成游戏样式的字符串\n",
    "    f = open(file=fn, encoding=\"utf8\", mode=\"a\")\n",
    "    res=[]\n",
    "    for i in range(0, len(arriveStDF)):\n",
    "        #format为对应顺序格式\n",
    "        tf = \"{train} : {arrive} {stop} {leave}\".format(\n",
    "            train=trainstr[i], arrive=arrivestr[i], stop=stopstr[i], leave=leavestr[i])\n",
    "        print(tf)\n",
    "        res.append(tf) #将结果写入文件\n",
    "        f.write(tf+\"\\n\")\n",
    "\n",
    "    f.close()\n",
    "    return res\n",
    "\n",
    "\n",
    "at = processTrains(\"test12306routet1.txt\",\"test12306ent.txt\", \"济南西\")\n",
    "#生成dataframe格式的数据\n",
    "arriveStDF = generateArriveLeave(at, mark=0)\n",
    "leaveStDF = generateArriveLeave(at, mark=1)\n",
    "stopStDF = generateMainSt(at,\"\")\n",
    "trainDF = generateTrainInfo(at[\"车次\"])\n",
    "\n",
    "#按照四部分生成需求的字符格式，使用to_csv函数来生成\n",
    "arrivestr = arriveStDF.to_csv(\n",
    "    sep=\"#\", header=False, index=False).split(sep=\"\\r\\n\")\n",
    "leavestr = leaveStDF.to_csv(sep=\"#\", header=False, index=False).split(sep=\"\\r\\n\")\n",
    "stopstr = stopStDF.to_csv(sep=\"#\", header=False, index=False).split(sep=\"\\r\\n\")\n",
    "trainstr = trainDF.to_csv(header=False, index=False).split(sep=\"\\r\\n\")\n",
    "\n",
    "generateStr(\"selenium测试.txt\")\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
